<?php
declare(strict_types=1);

namespace Dreamcat\ApiDoc\Popo;

use Dreamcat\ApiDoc\Enums\DataType;
use Dreamcat\ApiDoc\Error\PopoException;
use DreamCat\ObjectValid\Annotation\NumberScope;
use DreamCat\ObjectValid\Annotation\Pattern;
use DreamCat\ObjectValid\Annotation\Size;

/**
 * 消息体定义
 * @author vijay
 */
class BodyDefinition
{
    /** @var ?string 字段名 */
    private ?string $field = null;
    /** @var string 字段描述 */
    private string $desc = "";
    /** @var bool 是否必填 */
    private bool $isRequired = false;
    /** @var DataType 数据类型 */
    private DataType $dataType;
    /** @var BodyDefinition[] 对象类型时用来放字段定义，key是字段名 */
    private array $objectProperties;
    /**
     * @var ?BodyDefinition 子元素结构定义
     * @note 当数据类型是数组或map时用来表示子元素的结构
     */
    private ?BodyDefinition $childDefinition = null;
    /** @var NumberScope|null 数字的约束 */
    private ?NumberScope $numberScope = null;
    /** @var Pattern|null 正则约束 */
    private ?Pattern $pattern = null;
    /** @var Size|null 大小约定 */
    private ?Size $size = null;
    /** @var ?string 类型是map时描述key的定义 */
    private ?string $mapKeyDesc = null;
    /** @var ?string 类型是map时描述值的定义，一般不需要和$childDefinition共存 */
    private ?string $mapValueDesc = null;
    /** @var array 类型是枚举时，用来说明每个枚举的含义，key是值，value是描述 */
    private array $enums = [];
    /** @var ?string mock定义 */
    private ?string $mock = null;
    /** @var string|null 示例 */
    private ?string $example = null;
    /** @var mixed 默认值 */
    private mixed $default = null;

    public function __construct()
    {
        $this->dataType = DataType::UNKNOWN();
    }

    /**
     * @return ?string 字段名
     */
    public function getField(): ?string
    {
        return $this->field;
    }

    /**
     * @param ?string $field 字段名
     * @return static 对象本身
     */
    public function setField(?string $field): BodyDefinition
    {
        $this->field = $field;
        return $this;
    }

    /**
     * @return string 字段描述
     */
    public function getDesc(): string
    {
        return $this->desc;
    }

    /**
     * @param string $desc 字段描述
     * @return static 对象本身
     */
    public function setDesc(string $desc): BodyDefinition
    {
        $this->desc = $desc;
        return $this;
    }

    /**
     * @return bool 是否必填
     */
    public function isRequired(): bool
    {
        return $this->isRequired && is_null($this->default);
    }

    /**
     * @param bool $isRequired 是否必填
     * @return static 对象本身
     */
    public function setIsRequired(bool $isRequired): BodyDefinition
    {
        $this->isRequired = $isRequired;
        return $this;
    }

    /**
     * @return DataType 数据类型
     */
    public function getDataType(): DataType
    {
        return $this->dataType;
    }

    /**
     * @param DataType $dataType 数据类型
     * @return static 对象本身
     */
    public function setDataType(DataType $dataType): BodyDefinition
    {
        $this->dataType = $dataType;
        return $this;
    }

    /**
     * @return ?BodyDefinition 子元素结构定义
     */
    public function getChildDefinition(): ?BodyDefinition
    {
        return $this->childDefinition;
    }

    /**
     * @param ?BodyDefinition $childDefinition 子元素结构定义
     * @return static 对象本身
     */
    public function setChildDefinition(?BodyDefinition $childDefinition): BodyDefinition
    {
        $this->childDefinition = $childDefinition;
        return $this;
    }

    /**
     * @return NumberScope|null 数字的约束
     */
    public function getNumberScope(): ?NumberScope
    {
        return $this->numberScope;
    }

    /**
     * @param NumberScope|null $numberScope 数字的约束
     * @return static 对象本身
     */
    public function setNumberScope(?NumberScope $numberScope): BodyDefinition
    {
        $this->numberScope = $numberScope;
        return $this;
    }

    /**
     * @return Pattern|null 正则约束
     */
    public function getPattern(): ?Pattern
    {
        return $this->pattern;
    }

    /**
     * @param Pattern|null $pattern 正则约束
     * @return static 对象本身
     */
    public function setPattern(?Pattern $pattern): BodyDefinition
    {
        $this->pattern = $pattern;
        return $this;
    }

    /**
     * @return Size|null 大小约定
     */
    public function getSize(): ?Size
    {
        return $this->size;
    }

    /**
     * @param Size|null $size 大小约定
     * @return static 对象本身
     */
    public function setSize(?Size $size): BodyDefinition
    {
        $this->size = $size;
        return $this;
    }

    /**
     * @return string|null 类型是map时描述key的定义
     */
    public function getMapKeyDesc(): ?string
    {
        return $this->mapKeyDesc;
    }

    /**
     * @param string|null $mapKeyDesc 类型是map时描述key的定义
     * @return static 对象本身
     */
    public function setMapKeyDesc(?string $mapKeyDesc): BodyDefinition
    {
        $this->mapKeyDesc = $mapKeyDesc;
        return $this;
    }

    /**
     * @return string|null 类型是map时描述值的定义，一般不需要和$childDefinition共存
     */
    public function getMapValueDesc(): ?string
    {
        return $this->mapValueDesc;
    }

    /**
     * @param string|null $mapValueDesc 类型是map时描述值的定义，一般不需要和$childDefinition共存
     * @return static 对象本身
     */
    public function setMapValueDesc(?string $mapValueDesc): BodyDefinition
    {
        $this->mapValueDesc = $mapValueDesc;
        return $this;
    }

    /**
     * @return array 类型是枚举时，用来说明每个枚举的含义，key是值，value是描述
     */
    public function getEnums(): array
    {
        return $this->enums;
    }

    /**
     * 添加一个枚举描述
     * @param string $value 枚举值
     * @param string $desc 枚举描述
     * @return BodyDefinition
     */
    public function addEnumDesc(string $value, string $desc): BodyDefinition
    {
        $this->enums[$value] = $desc;
        return $this;
    }

    /**
     * @return ?string mock定义
     */
    public function getMock(): ?string
    {
        return $this->mock;
    }

    /**
     * @param string $mock mock定义
     * @return static 对象本身
     */
    public function setMock(string $mock): BodyDefinition
    {
        $this->mock = $mock;
        return $this;
    }

    /**
     * @return string|null 示例
     */
    public function getExample(): ?string
    {
        return $this->example;
    }

    /**
     * @param string|null $example 示例
     * @return static 对象本身
     */
    public function setExample(?string $example): BodyDefinition
    {
        $this->example = $example;
        return $this;
    }

    /**
     * @return BodyDefinition[] 对象类型时用来放字段定义，key是字段名
     */
    public function getObjectProperties(): array
    {
        return $this->objectProperties ?? [];
    }

    /**
     * 添加对象字段定义
     * @param BodyDefinition $definition 对象字段定义。字段名必须填写
     * @return $this
     */
    public function addObjectProperty(BodyDefinition $definition): BodyDefinition
    {
        $field = $definition->getField();
        if (is_null($field) || !strlen($field)) {
            throw new PopoException("对象属性必须定义字段名", PopoException::OBJECT_FIELD_CANT_EMPTY);
        }
        $this->objectProperties[$field] = $definition;
        return $this;
    }

    /**
     * 添加多层body
     * @param string[] $pathList 路径数组
     * @param BodyDefinition $body 要添加的数据体
     * @return void
     */
    public function addDeepChild(array $pathList, BodyDefinition $body): void
    {
        $root = $this;
        $last = array_pop($pathList);
        $body->setField($last);
        foreach ($pathList as $item) {
            if ($body->isRequired()) {
                $root->setIsRequired(true);
            }
            $child = $root->getObjectProperties();
            if (!isset($child[$item])) {
                $child[$item] = new BodyDefinition();
                $child[$item]->setDataType(DataType::OBJECT())->setField($item);
                $root->addObjectProperty($child[$item]);
            }
            $root = $child[$item];
        }
        if ($body->isRequired()) {
            $root->setIsRequired(true);
        }
        $root->addObjectProperty($body);
    }

    /**
     * @return mixed 默认值
     */
    public function getDefault(): mixed
    {
        return $this->default;
    }

    /**
     * @param mixed $default 默认值
     * @return static 对象本身
     */
    public function setDefault(mixed $default): BodyDefinition
    {
        $this->default = $default;
        return $this;
    }
}

# end of file
